#ifndef __OC_HttpClient_H__
#define __OC_HttpClient_H__

#include "StringUtil.h"

namespace OCPP
{
    class FtpClient;
    typedef std::function<void(const String&)>                   LogFnCallback;
    enum SettingsFlag
    {
        NO_FLAGS = 0x00,
        ENABLE_LOG = 0x01,
        VERIFY_PEER = 0x02,
        VERIFY_HOST = 0x04,
        ALL_FLAGS = 0xFF
    };

    class CurlHandle;
    class _MainExport HttpClient
    {
    public:
        static String UserAgent;
        static String Referer;

        // Public definitions
        typedef std::function<int(void*, double, double, double, double)> ProgressFnCallback;
        typedef std::unordered_map<String, String>              HeadersMap;
        typedef std::vector<char> ByteBuffer;

        /* This struct represents the form information to send on POST Form requests */
        struct PostFormInfo
        {
            PostFormInfo();
            ~PostFormInfo();

            /* Fill in the file upload field */
            void AddFormFile(const String& fieldName, const String& fieldValue);

            /* Fill in the filename or the submit field */
            void AddFormContent(const String& fieldName, const String& fieldValue);

            void* m_pFormPost;
            void* m_pLastFormptr;
        };

        // Progress Function Data Object - parameter void* of ProgressFnCallback references it
        struct ProgressFnStruct
        {
            ProgressFnStruct() : dLastRunTime(0), pCurl(nullptr), pOwner(nullptr) {}
            double dLastRunTime;
            void* pCurl;
            /* owner of the HttpClient object. can be used in the body of the progress
            * function to send signals to the owner (e.g. to update a GUI's progress bar)
            */
            void* pOwner;
        };

        // HTTP response data
        struct HttpResponse
        {
            HttpResponse() : iCode(0) {}
            int iCode; // HTTP response code
            HeadersMap mapHeaders; // HTTP response headers fields
            String strBody; // HTTP response body
        };



        /* Please provide your logger thread-safe routine, otherwise, you can turn off
        * error log messages printing by not using the flag ALL_FLAGS or ENABLE_LOG */
        explicit HttpClient(LogFnCallback oLogger);
        virtual ~HttpClient();

        // copy constructor and assignment operator are disabled
        HttpClient(const HttpClient& Copy) = delete;
        HttpClient& operator=(const HttpClient& Copy) = delete;

        // Setters - Getters (for unit tests)
        /*inline*/ void SetProgressFnCallback(void* pOwner, const ProgressFnCallback& fnCallback);
        /*inline*/ void SetProxy(const String& strProxy);
        inline void SetTimeout(const int& iTimeout) { m_iCurlTimeout = iTimeout; }
        inline void SetNoSignal(const bool& bNoSignal) { m_bNoSignal = bNoSignal; }
        inline void SetHTTPS(const bool& bEnableHTTPS) { m_bHTTPS = bEnableHTTPS; }
        inline auto GetProgressFnCallback() const
        {
            return m_fnProgressCallback.target<int(*)(void*, double, double, double, double)>();
        }
        inline void* GetProgressFnCallbackOwner() const { return m_ProgressStruct.pOwner; }
        inline const String& GetProxy() const { return m_strProxy; }
        inline const int GetTimeout() const { return m_iCurlTimeout; }
        inline const bool GetNoSignal() const { return m_bNoSignal; }
        inline const String& GetURL()      const { return m_strURL; }
        inline const unsigned char GetSettingsFlags() const { return m_eSettingsFlags; }
        inline const bool GetHTTPS() const { return m_bHTTPS; }

        // Session
        const bool InitSession(const bool& bHTTPS = false,
            const SettingsFlag& SettingsFlags = ALL_FLAGS);
        virtual const bool CleanupSession();

        // HTTP requests
        const bool GetText(const String& strURL,
            String& strText,
            long& lHTTPStatusCode);

        const bool DownloadFile(const String& strLocalFile,
            const String& strURL,
            long& lHTTPStatusCode);

        const bool DownloadFile(std::vector<unsigned char>& data, const String& strURL, long& lHTTPStatusCode);

        const bool UploadForm(const String& strURL,
            const PostFormInfo& data,
            long& lHTTPStatusCode);

        void AddHeader(const String& strHeader);

        // REST requests
        const bool Head(const String& strUrl, const HeadersMap& Headers, HttpResponse& Response);
        const bool Get(const String& strUrl, const HeadersMap& Headers, HttpResponse& Response);
        const bool Del(const String& strUrl, const HeadersMap& Headers, HttpResponse& Response);
        const bool Post(const String& strUrl, const HeadersMap& Headers,
            const String& strPostData, HttpResponse& Response);
        const bool Put(const String& strUrl, const HeadersMap& Headers,
            const String& strPutData, HttpResponse& Response);
        const bool Put(const String& strUrl, const HeadersMap& Headers,
            const ByteBuffer& Data, HttpResponse& Response);

        // SSL certs
        static const String& GetCertificateFile() { return s_strCertificationAuthorityFile; }
        static void SetCertificateFile(const String& strPath) { s_strCertificationAuthorityFile = strPath; }

        void SetSSLCertFile(const String& strPath) { m_strSSLCertFile = strPath; }
        const String& GetSSLCertFile() const { return m_strSSLCertFile; }

        void SetSSLKeyFile(const String& strPath) { m_strSSLKeyFile = strPath; }
        const String& GetSSLKeyFile() const { return m_strSSLKeyFile; }

        void SetSSLKeyPassword(const String& strPwd) { m_strSSLKeyPwd = strPwd; }
        const String& GetSSLKeyPwd() const { return m_strSSLKeyPwd; }
    protected:
        friend class FtpClient;
        // payload to upload on POST requests.
        struct UploadObject
        {
            UploadObject() : pszData(nullptr), usLength(0) {}
            const char* pszData; // data to upload
            size_t usLength; // length of the data to upload
        };

        /* common operations are performed here */
        inline const int Perform();
        inline void UpdateURL(const String& strURL);
        inline const bool InitRestRequest(const String& strUrl, const HeadersMap& Headers,
            HttpResponse& Response);
        inline const bool PostRestRequest(const int ePerformCode, HttpResponse& Response);

        // Curl callbacks
        static size_t WriteInStringCallback(void* ptr, size_t size, size_t nmemb, void* data);
        // static size_t WriteToFileCallback(void* ptr, size_t size, size_t nmemb, void* data);
        // static size_t WriteToMemoryCallback(void* ptr, size_t size, size_t nmemb, void* data);
        static size_t ReadFromFileCallback(void* ptr, size_t size, size_t nmemb, void* stream);
        static size_t ThrowAwayCallback(void* ptr, size_t size, size_t nmemb, void* data);
        static size_t RestWriteCallback(void* ptr, size_t size, size_t nmemb, void* userdata);
        static size_t RestHeaderCallback(void* ptr, size_t size, size_t nmemb, void* userdata);
        static size_t RestReadCallback(void* ptr, size_t size, size_t nmemb, void* userdata);

        // String Helpers
        // static String StringFormat(const String strFormat, ...);
        static inline void TrimSpaces(String& str);

        String          m_strURL;
        String          m_strProxy;

        bool                 m_bNoSignal;
        bool                 m_bHTTPS;
        SettingsFlag         m_eSettingsFlags;

        void* m_pHeaderlist;

        // SSL
        static String   s_strCertificationAuthorityFile;
        String          m_strSSLCertFile;
        String          m_strSSLKeyFile;
        String          m_strSSLKeyPwd;

        void* m_pCurlSession;
        int                  m_iCurlTimeout;

        // Progress function
        ProgressFnCallback    m_fnProgressCallback;
        ProgressFnStruct      m_ProgressStruct;
        bool                  m_bProgressCallbackSet;

        // Log printer callback
        LogFnCallback         m_oLog;

    private:
        CurlHandle& m_curlHandle;
    };

    inline SettingsFlag operator|(SettingsFlag a, SettingsFlag b) {
        return static_cast<SettingsFlag>(static_cast<int>(a) | static_cast<int>(b));
    }

    class _MainExport HttpClientEx : public HttpClient
    {
    public:
        HttpClientEx();
        HttpClientEx(LogFnCallback oLogger);
        ~HttpClientEx();
        String fetchContent(String url, long& retCode);
        bool fetchFile(String url, String fileName);
    };

    class _MainExport FtpClient
    {
    public:
        FtpClient(LogFnCallback oLogger);
        virtual ~FtpClient();

        const bool InitSession(const SettingsFlag& SettingsFlags = ALL_FLAGS);
        virtual const bool CleanupSession();

        const int Perform();
        inline void UpdateURL(const String& strURL);

        const bool DownloadFile(const String& strLocalFile,
            const String& strURL,
            long& lHTTPStatusCode);

        const bool DownloadMem(String& data,
            const String& strURL, 
            long& lHTTPStatusCode);

        const bool UploadFile(const String& strLocalFile,
            const String& strURL,
            long& lHTTPStatusCode);

        void setUser(String user) { m_strUser = user; }
        void setPassword(String pswd) { m_strPswd = pswd; }
    protected:
        int     m_iCurlTimeout = 0;
        long    m_lFileSize = 0;
        String m_strURL;
        String m_strUser;
        String m_strPswd;
        void* m_pCurlSession;
        LogFnCallback         m_oLog;
        SettingsFlag m_eSettingsFlags = NO_FLAGS;
        CurlHandle& m_curlHandle;
    };

    class _MainExport FtpClientEx : public FtpClient
    {
    public:
        FtpClientEx(String user = "", String pswd = "");
        FtpClientEx(String user, String pswd,LogFnCallback oLogger);
        ~FtpClientEx();

        const StringVector getFileList(const String& strURL);
        const bool DownloadToPath(const String& strURL, const String& strPath);
        const bool UploadFile(const String& strURL, const String& strFile);
    };
}

#endif
